/*
    实验思路
    上电先获取NVS中上一次存储的KV，等待按键按下后更新NVS,等待下一次上电重新读取
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "driver/gpio.h"

#define BOOT_MODE_PIN 9
#define STORAGE_NAMESPACE "storage"

/*从 NVS 读取并打印重启计数器和运行时间表*/
esp_err_t print_what_saved(void)
{
    nvs_handle_t h;
    esp_err_t ret;
    // open
    ret = nvs_open(STORAGE_NAMESPACE, NVS_READONLY, &h);
    if (ret != ESP_OK)
        return ret;

    // 读取重启计数器
    int32_t restart_counter = 0;
    ret = nvs_get_i32(h, "restart_conter", &restart_counter);
    if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND)
        return ret;
    printf("Restart counter = %" PRIu32 "\n", restart_counter);

    // 获取run time blob
    size_t required_size = 0;
    ret = nvs_get_blob(h, "run_time", NULL, &required_size);
    if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND)
        return ret;

    printf("Run time:\n");

    if (required_size == 0)
    {
        printf("Nothing saved yet!\n");
    }
    else
    {
        uint32_t *run_time = malloc(required_size);
        ret = nvs_get_blob(h, "run_time", run_time, &required_size);
        if (ret != ESP_OK)
        {
            free(run_time);
            return ret;
        }
        for (int i = 0; i < required_size / sizeof(uint32_t); i++)
        {
            printf("%d %" PRIu32 "\n", i + 1, run_time[i]);
        }
        free(run_time);
    }
    // Close
    nvs_close(h);
    return ESP_OK;
}
/* 每次上电更新restart counter */
esp_err_t update_restart_counter()
{
    nvs_handle_t h;
    esp_err_t err;

    // Open
    err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &h);
    if (err != ESP_OK)
        return err;

    // Read
    int32_t restart_counter = 0; // value will default to 0, if not set yet in NVS
    err = nvs_get_i32(h, "restart_conter", &restart_counter);
    if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND)
        return err;

    // Write
    restart_counter++;
    err = nvs_set_i32(h, "restart_conter", restart_counter);
    if (err != ESP_OK)
        return err;

    // Commit written value.
    // After setting any values, nvs_commit() must be called to ensure changes are written
    // to flash storage. Implementations may write to storage at other times,
    // but this is not guaranteed.
    err = nvs_commit(h);
    if (err != ESP_OK)
        return err;

    // Close
    nvs_close(h);
    return ESP_OK;
}

esp_err_t update_run_time(void)
{
    nvs_handle_t h;
    esp_err_t ret;
    // Open
    ret = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &h);
    if (ret != ESP_OK)
        return ret;

    // Read the size of memory space required for blob
    size_t required_size = 0; // value will default to 0, if not set yet in NVS
    ret = nvs_get_blob(h, "run_time", NULL, &required_size);
    if (ret != ESP_OK && ret != ESP_ERR_NVS_NOT_FOUND)
        return ret;

    // Read previously saved blob if available
    uint32_t *run_time = malloc(required_size + sizeof(uint32_t));
    if (required_size > 0)
    {
        ret = nvs_get_blob(h, "run_time", run_time, &required_size);
        if (ret != ESP_OK)
        {
            free(run_time);
            return ret;
        }
    }

    // Write value including previously saved blob if available
    required_size += sizeof(uint32_t);
    run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;
    ret = nvs_set_blob(h, "run_time", run_time, required_size);
    free(run_time);

    if (ret != ESP_OK)
        return ret;

    // Commit
    ret = nvs_commit(h);
    if (ret != ESP_OK)
        return ret;

    // Close
    nvs_close(h);
    return ESP_OK;
}

void app_main(void)
{
    /* 1、set boot key as input*/
    gpio_reset_pin(BOOT_MODE_PIN);
    gpio_set_direction(BOOT_MODE_PIN, GPIO_MODE_INPUT);

    /* 2、init nvs */
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {
        // NVS partition was truncated and needs to be erased
        // Retry nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);

    err = print_what_saved();
    if (err != ESP_OK)
        printf("Error (%s) reading data from NVS!\n", esp_err_to_name(err));

    err = update_restart_counter();
    if (err != ESP_OK)
        printf("Error (%s) saving restart counter to NVS!\n", esp_err_to_name(err));

    while (1)
    {
        if (gpio_get_level(BOOT_MODE_PIN) == 0)
        {
            vTaskDelay(1000 / portTICK_PERIOD_MS);
            if (gpio_get_level(BOOT_MODE_PIN) == 0)
            {
                err = update_run_time();
                if (err != ESP_OK)
                    printf("Error (%s) saving run time blob to NVS!\n", esp_err_to_name(err));
                printf("key press...\n");
                fflush(stdout);
                esp_restart();
            }
        }
        vTaskDelay(200 / portTICK_PERIOD_MS);
    }
}
